home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-04
/
wtjmarch.zip
/
LIFE.ZIP
/
CLIFE.CPP
< prev
next >
Wrap
C/C++ Source or Header
|
1992-01-27
|
19KB
|
539 lines
/****************************************************/
/* Life */
/* written in C++ */
/* Copyright (c) 1991 */
/* Zack Urlocker */
/* Translated from Pascal to C++ by Bruce Eckel */
/* 01/27/92 */
/****************************************************/
/* This is a simple implementation of the Game of Life originally
written in Turbo Pascal for Windows using the ObjectWindows
application framework. The program is divided into three main
object types:
TLifeCells --mutates and draws the cells in the window
TLifeWindow --responds to Windows messages, menu commands,
keyboard and mouse events
TLifeApplication --creates and shows the main window
*/
#include <owl.h>
#include <inputdia.h>
#include <string.h>
#include <time.h> // for randomize()
#include <stdlib.h> // for rand() and randomize()
const
cm_Clear = 201, /* command menu constant IDs */
cm_Go = 202,
cm_Trace = 203,
cm_Stop = 204,
cm_Exit = 209,
cm_About = 210,
cm_Timer = 301,
cm_Grid = 302,
cm_Zoom = 303,
cm_Random = 401,
cm_Bloom = 402,
cm_Walker = 403,
cm_Help = 501,
cm_CmdMode = 601, /* For Lotus style slash (/) key commands */
XMax = 100, /* Theoretical maximum matrix size */
YMax = 100; /* Actual size is based on zoom */
const unsigned
Black = 0x000000, /* Windows color constants */
White = 0xFFFFFF,
Blue = 0xFF0000,
Red = 0x0000FF;
const Boolean // (Boolean defined in header files)
Dead = False, /* cell values */
Born = True;
typedef Boolean matrix[XMax + 1][YMax + 1];
// The cells are responsible for mutating and drawing in a window.
// The cells will be notified whenever the size of the grid or
// number of rows and columns in the window changes.
class TLifeCells : public Object {
matrix cells; /* actual cells */
matrix scratchCells; /* scratch work area */
int C_rows; /* visible rows */
int C_cols; /* visible columns */
int gridSize; /* for drawing a cell */
public:
TLifeCells() { ClearCells(); } /* initialize cells */
void ClearCells() { /* Set all to dead */
memset(&cells, 0, sizeof(cells));
memset(&scratchCells, 0, sizeof scratchCells);
}
void mutate(HDC DC); /* mutate all cells */
void draw(HDC DC); /* draw all cells */
// Set the cell to born or dead state:
void setCell(int i, int j, Boolean State) { cells[i][j] = State; };
// Is the cell alive? :
Boolean aliveCell(int i, int j) { return cells[i][j]; };
void walker(int i, int j);
void bloom(int i, int j);
void mutateCell(HDC DC, int i, int j);
void drawCell(HDC DC, int i, int j, Boolean alive);
// Dummy definitions for Object pure virtual functions:
classType isA() const { return 0; }
char * nameOf() const { return "TLifeCells"; }
hashValueType hashValue() const { return 0; }
int isEqual( const Object& ) const { return 0; }
void printOn( ostream& ) const {}
friend class TLifeWindow; // give it access to private members
};
// The window responds to messages and controls the game board:
class TLifeWindow : public TWindow {
TLifeCells cells; /* cells being mutated */
int speed; /* timer speed */
Boolean running; /* is timer running? */
int rows; /* visible rows */
int cols; /* visible columns */
Boolean grid; /* is grid turned on? */
int gridSize; /* for drawing a cell */
Boolean mouseDown; /* is mouse down? */
int xDown; /* x location in grid */
int yDown; /* y location in grid */
HDC mutateDC; /* draw each mutation */
HDC mouseMoveDC; /* draw mouse moves */
public:
TLifeWindow(TWindowsObject* Parent, char* ATitle);
virtual void GetWindowClass(WNDCLASS& WndClass);
/* menu response methods */
virtual void Clear(TMessage& Msg) = [ CM_FIRST + cm_Clear ];
virtual void Randomize(TMessage& Msg) = [ CM_FIRST + cm_Random ];
virtual void Bloom(TMessage& Msg) = [ CM_FIRST + cm_Bloom ];
virtual void Walker(TMessage& Msg) = [ CM_FIRST + cm_Walker ];
virtual void Go(TMessage& Msg) = [ CM_FIRST + cm_Go ];
// Single step by stopping the timer and then mutate once:
virtual void Trace(TMessage& Msg) = [ CM_FIRST + cm_Trace ] {
TLifeWindow::Stop(Msg); // directly calling a message response method!
TLifeWindow::WMTimer(Msg);
}
virtual void Stop(TMessage& Msg) = [ CM_FIRST + cm_Stop ];
virtual void Exit(TMessage&) // Exit the program
= [ CM_FIRST + cm_Exit ] { PostQuitMessage(0); }
virtual void About(TMessage& ) // Display About box
= [ CM_FIRST + cm_About ] {
TDialog *Dl = new TDialog(this, "AboutDlg");
// Dl->Execute();
GetApplication()->ExecDialog(Dl);
}
virtual void Timer(TMessage& Msg) = [ CM_FIRST + cm_Timer ];
virtual void GridToggle(TMessage& Msg) = [ CM_FIRST + cm_Grid ];
virtual void Zoom(TMessage& Msg) = [ CM_FIRST + cm_Zoom ];
virtual void Help(TMessage&) = [ CM_FIRST + cm_Help ] {
TDialog *Dl = new TDialog(this, "HelpDlg");
// Dl->Execute();
GetApplication()->ExecDialog(Dl);
}
// Respond to Lotus style commands from slash (/) accelerator:
virtual void CmdMode(TMessage&) = [ CM_FIRST + cm_CmdMode ] {
SendMessage(HWindow, WM_SYSCOMMAND, 0xF100, 0);
}
virtual void Paint(HDC DC, PAINTSTRUCT& PaintInfo);
void DrawGrid(HDC DC);
/* windows message response methods */
// Ensure that cursor is visible even when no mouse:
virtual void WMSetFocus(TMessage&)
= [ WM_FIRST + WM_SETFOCUS ] { ShowCursor(True); }
// Return cursor to previous state for other windows:
virtual void WMKillFocus(TMessage&)
= [ WM_FIRST + WM_KILLFOCUS ] { ShowCursor(False); }
virtual void WMKeyDown(TMessage& Msg) = [ WM_FIRST + WM_KEYDOWN ];
virtual void WMLButtonDown(TMessage& Msg) = [ WM_FIRST + WM_LBUTTONDOWN ];
virtual void WMLButtonUp(TMessage& Msg) = [ WM_FIRST + WM_LBUTTONUP ];
virtual void WMMouseMove(TMessage& Msg) = [ WM_FIRST + WM_MOUSEMOVE ];
// Zoom when right mouse button is pressed:
virtual void WMRButtonDown(TMessage& Msg)
= [WM_FIRST + WM_RBUTTONDOWN] { TLifeWindow::Zoom(Msg); }
virtual void WMTimer(TMessage& Msg) = [ WM_FIRST + WM_TIMER ];
virtual void WMSize(TMessage& Msg) = [ WM_FIRST + WM_SIZE ];
// When the window is destroyed, stop any timers:
virtual void WMDestroy(TMessage& Msg) = [ WM_FIRST + WM_DESTROY ] {
KillTimer(HWindow, 1);
TWindow::WMDestroy(Msg);
}
};
/*--------------------------------------------------*/
/* TLifeCell's method implementations: */
/*--------------------------------------------------*/
void TLifeCells::walker(int i, int j) {
cells[i][j+2] = Born;
cells[i+1][j+2] = Born;
cells[i+2][j+2] = Born;
cells[i+2][j+1] = Born;
cells[i+1][j] = Born;
}
void TLifeCells::bloom(int i, int j) {
cells[i+1][j] = Born;
cells[i][j+1] = Born;
cells[i][j+2] = Born;
cells[i][j+3] = Born;
cells[i+1][j+3] = Born;
cells[i+2][j+3] = Born;
cells[i+2][j+2] = Born;
cells[i+2][j+1] = Born;
}
/* Draw a single cell as a borderless rectangle */
void TLifeCells::drawCell(HDC DC, int i, int j, Boolean alive) {
/* There was an off-by-one bug here previously */
int xScreen = i * gridSize;
int yScreen = j * gridSize;
int brushcolor = (alive ? BLACK_BRUSH : WHITE_BRUSH);
SelectObject(DC, GetStockObject(brushcolor));
Rectangle(DC, xScreen+1, yScreen+1, xScreen+gridSize, yScreen+gridSize);
DeleteObject(SelectObject(DC, GetStockObject(BLACK_BRUSH)));
}
/* Redraw active cells on screen */
void TLifeCells::draw(HDC DC) {
for(int i= 1 ; i <= C_cols ; i++ )
for(int j = 1 ; j <= C_rows ; j++)
if (cells[i][j])
drawCell(DC, i, j, Born);
}
/* Determine how the cell should mutate by the number of neighbors
it has. Too few or too many means it should die. */
void TLifeCells::mutateCell(HDC DC, int i, int j) {
int neighbors = 0;
if (cells[i-1][j]) ++neighbors;
if (cells[i+1][j]) ++neighbors;
if (cells[i][j-1]) ++neighbors;
if (cells[i][j+1]) ++neighbors;
if (cells[i-1][j-1]) ++neighbors;
if (cells[i+1][j+1]) ++neighbors;
if (cells[i-1][j+1]) ++neighbors;
if (cells[i+1][j-1]) ++neighbors;
if ( cells[i][j] == False ) /* it's a dead cell */
if (neighbors == 3) { /* bring it to life */
scratchCells[i][j] = Born;
drawCell(DC, i, j, Born);
}
else
scratchCells[i][j] = cells[i][j];
else /* it's a live cell */
if ((neighbors < 2) || (neighbors > 3)) { /* kill it */
scratchCells[i][j] = Dead;
drawCell(DC, i, j, Dead);
}
else
scratchCells[i][j] = cells[i][j];
}
/* Mutate all of the visible cells */
void TLifeCells::mutate(HDC DC) {
for (int i = 1; i <= C_cols; i++)
for (int j = 1; j <= C_rows; j++ )
mutateCell(DC, i, j);
/* update the real matrix */
memcpy(cells, scratchCells, sizeof(cells));
}
/*--------------------------------------------------*/
/* TLifeWindow's method implementations: */
/*--------------------------------------------------*/
/* Initialize all fields to starting values */
TLifeWindow::TLifeWindow(TWindowsObject* AParent, char* ATitle) :
TWindow(AParent, ATitle) {
running = False;
speed = 500;
grid = True;
gridSize = 20;
cells.gridSize = 20;
mouseDown = False;
Attr.W=400; /* Force window size */
Attr.H=300;
}
/* Override default cursor, icon, menu */
void TLifeWindow::GetWindowClass(WNDCLASS& WndClass) {
TWindow::GetWindowClass(WndClass); // call base-class version
WndClass.style = 0;
WndClass.hCursor = LoadCursor(WndClass.hInstance, "LifeCur");
WndClass.hIcon = LoadIcon(WndClass.hInstance, "LifeIco");
WndClass.lpszMenuName = "LifeMenu";
}
// Respond to a timer tick by mutating the cells.
// This is the main activity of the program.
void TLifeWindow::WMTimer(TMessage& ) {
mutateDC = GetDC(HWindow);
// Use a white pen for the border, then set it back when done:
SelectObject(mutateDC, GetStockObject(WHITE_PEN));
cells.mutate(mutateDC);
SelectObject(mutateDC, GetStockObject(BLACK_PEN));
ReleaseDC(HWindow, mutateDC);
}
/* Randomly create a starting pattern */
void TLifeWindow::Randomize(TMessage& Msg) {
TLifeWindow::Clear(Msg);
for (int i= 1; i <= cols; i++ )
for (int j = 1 ; j <= rows ; j++ )
if (random(100) < 25 )
cells.setCell(i, j, Born);
InvalidateRect(HWindow, 0, True);
}
inline int odd(int arg) { return arg % 2; } // is arg odd?
/* Create a non-random starting pattern */
void TLifeWindow::Bloom(TMessage& Msg) {
TLifeWindow::Clear(Msg);
for (int i = 0 ; i <= cols / 7 ; i++)
for (int j = 0 ; j <= rows / 7 ; j++)
if ( !odd(i+j) )
cells.bloom(4+i*7, 2+j*7);
InvalidateRect(HWindow, 0, True);
}
/* Create a non-random starting pattern */
void TLifeWindow::Walker(TMessage& Msg) {
TLifeWindow::Clear(Msg);
for (int i = 0 ; i <= cols / 7 ; i++)
for (int j = 0 ; j <= rows / 7 ; j++)
if ( !odd(i+j) )
cells.walker(2+i*7, 2+j*7);
InvalidateRect(HWindow, 0, True);
}
/* Start the timer and update the menus */
void TLifeWindow::Go(TMessage& ) {
if (SetTimer(HWindow, 1, speed, 0) != 0 ) {
running = True;
ModifyMenu(GetMenu(HWindow), cm_Go, MF_BYCOMMAND | MF_GRAYED,
cm_Go, "&Go" "\0x9" "^G");
ModifyMenu(GetMenu(HWindow), cm_Stop, MF_BYCOMMAND | MF_ENABLED,
cm_Stop, "&Stop" "\0x9" "^S");
} else {
running = False;
MessageBeep(0);
MessageBox(HWindow, "No timers left to run Life;" "\0xD"
"Close some windows and retry!" ,
"Error", MB_OK + MB_ICONSTOP);
}
}
/* Stop the timers and update the menus */
void TLifeWindow::Stop(TMessage& ) {
ModifyMenu(GetMenu(HWindow), cm_Go, MF_BYCOMMAND | MF_ENABLED,
cm_Go, "&Go" "\0x9" "^G");
ModifyMenu(GetMenu(HWindow), cm_Stop, MF_BYCOMMAND | MF_GRAYED,
cm_Stop, "&Stop" "\0x9" "^S");
running = False;
KillTimer(HWindow, 1);
}
/* Stop current timer, prompt for new speed, restart */
void TLifeWindow::Timer(TMessage& Msg) {
TLifeWindow::Stop(Msg);
const sz = 10; char InputText[sz];
itoa(speed, InputText, sz); // initialize buffer with current speed
TInputDialog* InDlg = new TInputDialog(this,
"Timer Speed", "Input new time (milliseconds):",
InputText, sizeof(InputText));
if (InDlg->Execute() == IDOK ) {
int NewSpeed = atoi(InputText);
if(NewSpeed > 0)
speed = NewSpeed;
} else
MessageBeep(0);
TLifeWindow::Go(Msg); // start timers
}
/* Stop, clear the matrix, restart */
void TLifeWindow::Clear(TMessage& Msg) {
Boolean paused = running;
TLifeWindow::Stop(Msg); // stop timers
cells.ClearCells();
InvalidateRect(HWindow, 0, True);
if (paused)
TLifeWindow::Go(Msg); // start timers
}
/* Toggle the displaying of the grid and redraw */
void TLifeWindow::GridToggle(TMessage& ) {
unsigned style;
grid = (Boolean)!grid;
if(grid)
style = MF_CHECKED;
else
style = MF_UNCHECKED;
CheckMenuItem(GetMenu(HWindow), cm_Grid, style);
DrawMenuBar(HWindow);
InvalidateRect(HWindow, 0, True);
}
/* Zoom the display, update internal info then redraw */
void TLifeWindow::Zoom(TMessage& ) {
gridSize = gridSize * 2;
if (gridSize > 50 )
gridSize = 10;
cols = Attr.W / gridSize;
rows = Attr.H / gridSize;
/* update the cells */
cells.C_rows = rows;
cells.C_cols = cols;
cells.gridSize = gridSize;
InvalidateRect(HWindow, 0, True);
}
/* Draw the grid and the cells */
void TLifeWindow::Paint(HDC DC, PAINTSTRUCT& ) {
SelectObject(DC, GetStockObject(BLACK_PEN));
if (grid) DrawGrid(DC);
SelectObject(DC, GetStockObject(WHITE_PEN));
cells.draw(DC);
}
/* Draw the grid background. */
void TLifeWindow::DrawGrid(HDC DC) {
for (int i = 1; i <= rows; i++) {
MoveTo(DC, 0, i*gridSize);
LineTo(DC, Attr.W, i*gridSize);
}
for (i = 1; i <= cols; i++) {
MoveTo(DC, i*gridSize, 0);
LineTo(DC, i*gridSize, Attr.H);
}
}
/* Use keyboard to simulate mouse events. Accelerator keys
are handled as response methods. */
void TLifeWindow::WMKeyDown(TMessage& Msg) {
int x, y;
POINT pos;
/* Determine position of cursor in Window */
GetCursorPos(&pos);
ScreenToClient(HWindow, &pos);
x=pos.x;
y=pos.y;
/* move the cursor position */
switch(Msg.WParam) {
case VK_UP : y = y - gridSize; break;
case VK_DOWN : y = y + gridSize; break;
case VK_RIGHT : x = x + gridSize; break;
case VK_LEFT : x = x - gridSize; break;
case VK_HOME : x = gridSize / 2;
y = gridSize / 2;
break;
case VK_END : x = Attr.W - gridSize / 2;
y = Attr.H - gridSize / 2;
break;
case VK_RETURN :
case VK_SPACE :
SendMessage(HWindow, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(pos.x, pos.y));
SendMessage(HWindow, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(pos.x, pos.y));
break;
}
/* Update position of cursor in window with clipping */
if (x < 0 ) x = gridSize / 2;
if (y < 0 ) y = gridSize / 2;
if (x > cols * gridSize ) x = (Attr.W - gridSize) / 2;
if (y > rows * gridSize ) y = (Attr.H - gridSize) / 2;
pos.x = x;
pos.y = y;
ClientToScreen(HWindow, &pos);
SetCursorPos(pos.x, pos.y);
}
/* Capture mouse movement when the left button is pressed. A display
context is taken; it is freed in the WMLButtonUp method. */
void TLifeWindow::WMLButtonDown(TMessage& ) {
if (!mouseDown ) {
xDown = -1; /* sentinel values to track movement */
yDown = -1;
mouseDown = True;
mouseMoveDC = GetDC(HWindow);
SelectObject(mouseMoveDC, GetStockObject(WHITE_PEN));
}
}
/* Update the cells as the mouse is dragged */
void TLifeWindow::WMMouseMove(TMessage& Msg) {
int xScreen, yScreen, x, y;
Boolean state;
if (mouseDown ) {
/* determine where clicked */
xScreen = LOWORD(Msg.LParam);
yScreen = HIWORD(Msg.LParam);
/* translate into cell coordinates */
x = xScreen / gridSize;
y = yScreen / gridSize;
if ((x != xDown) || (y != yDown) ) { /* a new position */
/* Invert the cell's state, then redraw */
xDown = x; /* store position */
yDown = y;
state = (Boolean)!(cells.aliveCell(x, y));
cells.setCell(x, y, state);
cells.drawCell(mouseMoveDC, x, y, state);
}
}
}
/* Stop capturing mouse movement when mouse is released */
void TLifeWindow::WMLButtonUp(TMessage& Msg) {
// force drawing in same spot:
TLifeWindow::WMMouseMove(Msg);
if (mouseDown ) {
mouseDown = False;
SelectObject(mouseMoveDC, GetStockObject(BLACK_PEN));
ReleaseDC(HWindow, mouseMoveDC);
}
}
/* update internal information when resizing then redraw */
void TLifeWindow::WMSize(TMessage& Msg) {
cells.C_rows = rows = HIWORD(Msg.LParam) / gridSize;
cells.C_cols = cols = LOWORD(Msg.LParam) / gridSize;
Attr.H = HIWORD(Msg.LParam);
Attr.W = LOWORD(Msg.LParam);
InvalidateRect(HWindow, 0, True);
}
// The application defines startup behavior for the window:
class TLifeApplication : public TApplication {
public:
TLifeApplication (LPSTR Name, HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
: TApplication(Name, hInstance, hPrevInstance,
lpCmdLine, nCmdShow) {};
virtual void InitInstance() {
TApplication::InitInstance(); // call base class version
// Load the accelerator table for hotkeys:
HAccTable = LoadAccelerators(hInstance, "LifeKeys");
}
/* Start the main window */
virtual void InitMainWindow() {
MainWindow = new TLifeWindow(0, "CLife");
}
};
/*--------------------------------------------------*/
/* Main program: */
/*--------------------------------------------------*/
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
TLifeApplication MyApp("CLife",hInstance,
hPrevInstance,lpCmdLine, nCmdShow);
MyApp.Run();
return MyApp.Status;
}